﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Linq;
using System.Windows.Data;

namespace Gmantis.Controls.Extended
{
    public enum Office2007ColorTheme
    {
        Standard,
        Highlight,
        Office,
        GrayScale,
        Apex,
        Aspect,
        Civic,
        Concourse,
        Equity,
        Flow,
        Foundry,
        Median,
        Metro,
        Module,
        Opulent,
        Oriel,
        Origin,
        Paper,
        Solstice,
        Technic,
        Trek,
        Urban,
        Verve
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct ColorInfo
    {
        public Color Color { get; set; }
        public Point Location { get; set; }
    }

    [TemplatePart(Name = "ThemeColorsValues", Type = typeof(Grid)), 
    TemplatePart(Name = "StandardColors", Type = typeof(Grid)), 
    TemplatePart(Name = "ThemeColorsHeader", Type = typeof(Grid)), 
    TemplatePart(Name = "RecentColors", Type = typeof(Grid)), 
    StyleTypedProperty(Property = "ColorContainerStyle", StyleTargetType = typeof(ItemsControl)), 
    TemplatePart(Name = "RecentColorsHeader", Type = typeof(FrameworkElement)), 
    TemplatePart(Name = "TransparentColor", Type = typeof(Grid))]
    public class BasicColorPickerPart : Control
    {
        // Fields
        private Dictionary<Point, ColorButton> _buttons = new Dictionary<Point, ColorButton>();
        private Point _currentSelection = new Point();
        internal Grid _elementRecentColors;
        internal FrameworkElement _elementRecentColorsHeader;
        internal Grid _elementStandardColors;
        internal Grid _elementThemeColorsHeader;
        internal Grid _elementThemeColorsValues;
        internal Grid _elementTransparentColor;
        internal bool _isLoaded;
        private FixedQueue<Color> _recentColors = new FixedQueue<Color>(10);
        private bool _throwSelectedColorChanged = true;
        public static readonly DependencyProperty ColorContainerStyleProperty = DependencyProperty.Register("ColorContainerStyle", typeof(Style), typeof(BasicColorPickerPart), null);
        public static readonly DependencyProperty PaletteProperty = DependencyProperty.Register("Palette", typeof(ColorPalette), typeof(BasicColorPickerPart), new PropertyMetadata(ColorPalette.GetColorPalette(Office2007ColorTheme.Office), new PropertyChangedCallback(BasicColorPickerPart.OnPalettePropertyChanged)));
        internal const string RecentColorsElementName = "RecentColors";
        internal const string RecentColorsHeaderElementName = "RecentColorsHeader";
        public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register("SelectedColor", typeof(Color), typeof(BasicColorPickerPart), new PropertyMetadata(Colors.White, new PropertyChangedCallback(BasicColorPickerPart.OnSelectedColorPropertyChanged)));
        public static readonly DependencyProperty ShowRecentColorsProperty = DependencyProperty.Register("ShowRecentColors", typeof(bool), typeof(BasicColorPickerPart), new PropertyMetadata(true, new PropertyChangedCallback(BasicColorPickerPart.OnShowRecentColorsPropertyChanged)));
        public static readonly DependencyProperty ShowTransparentColorProperty = DependencyProperty.Register("ShowTransparentColor", typeof(bool), typeof(BasicColorPickerPart), null);
        internal const string StandardColorsElementName = "StandardColors";
        internal const string ThemeColorsHeaderElementName = "ThemeColorsHeader";
        internal const string ThemeColorsValuesElementName = "ThemeColorsValues";
        internal const string TransparentColorElementName = "TransparentColor";

        // Events
        public event EventHandler ColorPicked;
        public event EventHandler<PropertyChangedEventArgs<Color>> SelectedColorChanged;

        // Methods
        public BasicColorPickerPart()
        {
            base.DefaultStyleKey = typeof(BasicColorPickerPart);
            SetCustomDefaultValues();
        }

        private void AddContainer(Grid grid, int col, params Color[] colors)
        {
            int rowCount = this.GetRowCount(col);
            ItemsControl control = this.CreateColorContainer();
            for (int i = 0; i < colors.Length; i++)
            {
                ColorButton button = this.CreateColorButton(colors[i]);
                control.Items.Add(button);
                Point key = new Point((double)col, (double)((rowCount + i) + 1));
                ColorInfo info = new ColorInfo
                {
                    Color = colors[i],
                    Location = key
                };
                button.Tag = info;
                this._buttons.Add(key, button);
            }
            if (grid.ColumnDefinitions.Count < (col + 1))
            {
                ColumnDefinition definition = new ColumnDefinition
                {
                    Width = GridLength.Auto
                };
                grid.ColumnDefinitions.Add(definition);
            }
            grid.Children.Add(control);
            Grid.SetColumn(control, col);
        }

        private void btn_Click(object sender, RoutedEventArgs e)
        {
            ColorButton button = (ColorButton)sender;
            ColorInfo tag = (ColorInfo)button.Tag;
            this.SelectedColor = tag.Color;
            this.RaiseColorPicked();
        }

        private void btn_MouseEnter(object sender, MouseEventArgs e)
        {
            ColorButton button = (ColorButton)sender;
            ColorInfo tag = (ColorInfo)button.Tag;
            this.CurrentSelection = tag.Location;
        }

        protected virtual ColorButton CreateColorButton(ColorPaletteEntry colorEntry)
        {
            ColorButton element = new ColorButton();
            ToolTipService.SetToolTip(element, colorEntry.ColorName);
            element.Color = colorEntry.Color;
            element.Click += new RoutedEventHandler(this.btn_Click);
            element.MouseEnter += new MouseEventHandler(this.btn_MouseEnter);
            element.SetBinding(Control.BorderBrushProperty, new Binding().From<BasicColorPickerPart>(this, x => x.BorderBrush));
            return element;
        }

        protected virtual ColorButton CreateColorButton(Color color)
        {
            return this.CreateColorButton(new ColorPaletteEntry(color));
        }

        protected virtual ItemsControl CreateColorContainer()
        {
            return new ItemsControl { Style = this.ColorContainerStyle };
        }

        private int GetColCount(int row)
        {
            IEnumerable<int> source = from b in this._buttons
                                      where b.Key.Y == row
                                      select b.Key.X();
            if (source.Count<int>() <= 0)
            {
                return -1;
            }
            return source.Max();
        }

        private int GetRowCount(int col)
        {
            IEnumerable<int> source = from b in this._buttons
                                      where b.Key.X == col
                                      select b.Key.Y();
            if (source.Count<int>() <= 0)
            {
                return -1;
            }
            return source.Max();
        }

        private T GetTemplateChild<T>(string childName, bool required, ref string errors) where T : class
        {
            DependencyObject templateChild = base.GetTemplateChild(childName);
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "template part", childName, templateChild, required, ref errors);
            return (templateChild as T);
        }

        private static Storyboard GetTemplateChildResource(FrameworkElement root, string resourceName, bool required, ref string errors)
        {
            return GetTemplateChildResource<Storyboard>(root, resourceName, required, ref errors);
        }

        private static T GetTemplateChildResource<T>(FrameworkElement root, string resourceName, bool required, ref string errors) where T : class
        {
            object child = root.Resources[resourceName];
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "resource", resourceName, child, required, ref errors);
            return (child as T);
        }

        private void HandleKeyboard(object sender, System.Windows.Input.KeyEventArgs e)
        {
            int col = this.CurrentSelection.X();
            int row = this.CurrentSelection.Y();
            int num3 = this.GetColCount(row) + 1;
            int num4 = this.GetRowCount(col) + 1;
            switch (e.Key)
            {
                case Key.Enter:
                    {
                        ColorInfo tag = (ColorInfo)this._buttons[this.CurrentSelection].Tag;
                        this.SelectedColor = tag.Color;
                        e.Handled = true;
                        this.RaiseColorPicked();
                        break;
                    }
                case Key.End:
                    row = this.GetRowCount(0);
                    col = this.GetColCount(row);
                    e.Handled = true;
                    break;

                case Key.Home:
                    col = 0;
                    row = 0;
                    e.Handled = true;
                    break;

                case Key.Left:
                    col = ((col + num3) - 1) % num3;
                    e.Handled = true;
                    break;

                case Key.Up:
                    row = ((row + num4) - 1) % num4;
                    e.Handled = true;
                    break;

                case Key.Right:
                    col = ((col + num3) + 1) % num3;
                    e.Handled = true;
                    break;

                case Key.Down:
                    row = ((row + num4) + 1) % num4;
                    e.Handled = true;
                    break;
            }
            this.CurrentSelection = new Point((double)col, (double)row);
        }

        private void HideCurrentSelected()
        {
            this.UpdateCurrentSelection(null);
        }

        private void OnAfterApplyTemplate()
        {
            this.Populate();
            base.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(this.HandleKeyboard), true);
        }

        public override void OnApplyTemplate()
        {
            string errors = string.Empty;
            base.OnApplyTemplate();
            this._isLoaded = true;
            this._elementStandardColors = this.GetTemplateChild<Grid>("StandardColors", true, ref errors);
            this._elementRecentColors = this.GetTemplateChild<Grid>("RecentColors", true, ref errors);
            this._elementThemeColorsHeader = this.GetTemplateChild<Grid>("ThemeColorsHeader", true, ref errors);
            this._elementThemeColorsValues = this.GetTemplateChild<Grid>("ThemeColorsValues", true, ref errors);
            this._elementRecentColorsHeader = this.GetTemplateChild<FrameworkElement>("RecentColorsHeader", false, ref errors);
            this._elementTransparentColor = this.GetTemplateChild<Grid>("TransparentColor", false, ref errors);
            if (!string.IsNullOrEmpty(errors))
            {
                this._isLoaded = false;
                if ((base.Template != null) && !PlatformIndependent.IsInDesignMode(this))
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Template cannot be applied to BasicColorPickerPart.\nDetails: {0}", new object[] { errors }));
                }
            }
            else
            {
                this.OnAfterApplyTemplate();
            }
        }

        private void OnPaletteChanged(ColorPalette oldValue)
        {
            this.Populate();
        }

        private static void OnPalettePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BasicColorPickerPart part = d as BasicColorPickerPart;
            ColorPalette oldValue = (ColorPalette)e.OldValue;
            part.OnPaletteChanged(oldValue);
        }

        private void OnSelectedColorChanged(Color oldValue)
        {
            this.UpdateRecentColors();
        }

        private static void OnSelectedColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BasicColorPickerPart sender = d as BasicColorPickerPart;
            Color oldValue = (Color)e.OldValue;
            sender.OnSelectedColorChanged(oldValue);
            if ((sender.SelectedColorChanged != null) && sender._throwSelectedColorChanged)
            {
                PropertyChangedEventArgs<Color> args = new PropertyChangedEventArgs<Color>
                {
                    OldValue = (Color)e.OldValue,
                    NewValue = (Color)e.NewValue
                };
                sender.SelectedColorChanged(sender, args);
            }
        }

        private void OnShowRecentColorsChanged(bool oldValue)
        {
        }

        private static void OnShowRecentColorsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BasicColorPickerPart part = d as BasicColorPickerPart;
            bool oldValue = (bool)e.OldValue;
            part.OnShowRecentColorsChanged(oldValue);
        }

        private void Populate()
        {
            if (this._isLoaded)
            {
                this._buttons.Clear();
                this.PopulatePaletteColors();
                this.PopulateExpandedPaletteColors();
                this.PopulateStandardColors();
                this.PopulateRecentColors();
                this.PopulateTransparentColor();
            }
        }

        private void PopulateExpandedPaletteColors()
        {
            this._elementThemeColorsValues.Children.Clear();
            for (int i = 0; i < this.Palette.Count; i++)
            {
                ColorPaletteEntry entry = this.Palette[i];
                HslColor color = new HslColor(entry.Color);
                int num2 = (int)(color.Luminance * 256f);
                if (num2 == 0)
                {
                    Color[] colors = new Color[] { color.Lighter(50).ToColor(), color.Lighter(0x23).ToColor(), color.Lighter(0x19).ToColor(), color.Lighter(15).ToColor(), color.Lighter(5).ToColor() };
                    this.AddContainer(this._elementThemeColorsValues, i, colors);
                }
                else if (num2 < 0x33)
                {
                    Color[] colorArray2 = new Color[] { color.Lighter(90).ToColor(), color.Lighter(0x4b).ToColor(), color.Lighter(50).ToColor(), color.Lighter(0x19).ToColor(), color.Lighter(10).ToColor() };
                    this.AddContainer(this._elementThemeColorsValues, i, colorArray2);
                }
                else if (num2 < 0xcc)
                {
                    Color[] colorArray3 = new Color[] { color.Lighter(80).ToColor(), color.Lighter(60).ToColor(), color.Lighter(40).ToColor(), color.Lighter(-25).ToColor(), color.Lighter(-50).ToColor() };
                    this.AddContainer(this._elementThemeColorsValues, i, colorArray3);
                }
                else if (num2 < 0xff)
                {
                    Color[] colorArray4 = new Color[] { color.Lighter(-10).ToColor(), color.Lighter(-25).ToColor(), color.Lighter(-50).ToColor(), color.Lighter(-75).ToColor(), color.Lighter(-90).ToColor() };
                    this.AddContainer(this._elementThemeColorsValues, i, colorArray4);
                }
                else
                {
                    Color[] colorArray5 = new Color[] { color.Lighter(-5).ToColor(), color.Lighter(-15).ToColor(), color.Lighter(-25).ToColor(), color.Lighter(-35).ToColor(), color.Lighter(-50).ToColor() };
                    this.AddContainer(this._elementThemeColorsValues, i, colorArray5);
                }
            }
        }

        private void PopulatePaletteColors()
        {
            this._elementThemeColorsHeader.Children.Clear();
            for (int i = 0; i < this.Palette.Count; i++)
            {
                Color[] colors = new Color[] { this.Palette[i].Color };
                this.AddContainer(this._elementThemeColorsHeader, i, colors);
            }
        }

        private void PopulateRecentColors()
        {
            if (this._elementRecentColors != null)
            {
                this._elementRecentColors.Children.Clear();
                IList<Color> list = this._recentColors.ToList();
                for (int i = 0; i < this._recentColors.Count; i++)
                {
                    Color[] colors = new Color[] { list[i] };
                    this.AddContainer(this._elementRecentColors, i, colors);
                }
            }
        }

        private void PopulateStandardColors()
        {
            this._elementStandardColors.Children.Clear();
            ColorPalette colorPalette = ColorPalette.GetColorPalette(Office2007ColorTheme.Standard);
            for (int i = 0; i < colorPalette.Count; i++)
            {
                Color[] colors = new Color[] { colorPalette[i].Color };
                this.AddContainer(this._elementStandardColors, i, colors);
            }
        }

        private void PopulateTransparentColor()
        {
            if (this._elementTransparentColor != null)
            {
                this._elementTransparentColor.Children.Clear();
                Color[] colors = new Color[] { Colors.Transparent };
                this.AddContainer(this._elementTransparentColor, 0, colors);
            }
        }

        private void RaiseColorPicked()
        {
            if (this.ColorPicked != null)
            {
                this.ColorPicked(this, new EventArgs());
            }
        }

        private void SetCurrentSelection(Color color)
        {
            foreach (KeyValuePair<Point, ColorButton> pair in this._buttons)
            {
                if (pair.Value.Color == color)
                {
                    this.CurrentSelection = pair.Key;
                    break;
                }
            }
        }

        private void SetCustomDefaultValues()
        {
            base.GotFocus += (s, e) => this.ShowCurrentSelected();
            base.LostFocus += (s, e) => this.HideCurrentSelected();
        }

        private void ShowCurrentSelected()
        {
            this.UpdateCurrentSelection(new Point?(this._currentSelection));
        }

        private void UpdateCurrentSelection(Point? value)
        {
            foreach (KeyValuePair<Point, ColorButton> pair in this._buttons)
            {
                pair.Value.IsSelected = pair.Key == value;
            }
        }

        private void UpdateRecentColors()
        {
            if (this._recentColors.Contains(this.SelectedColor))
            {
                this._recentColors.ForceEnqueue(this.SelectedColor);
            }
            else
            {
                this._recentColors.Enqueue(this.SelectedColor);
            }
            this.PopulateRecentColors();
        }

        // Properties
        public Style ColorContainerStyle
        {
            get { return (Style)GetValue(ColorContainerStyleProperty); }
            set { SetValue(ColorContainerStyleProperty, value); }
        }

        private Point CurrentSelection
        {
            get { return this._currentSelection; }
            set
            {
                this._currentSelection = value;
                this.UpdateCurrentSelection(new Point?(this._currentSelection));
            }
        }

        public ColorPalette Palette
        {
            get { return (ColorPalette)GetValue(PaletteProperty); }
            set { SetValue(PaletteProperty, value); }
        }

        public Color SelectedColor
        {
            get { return (Color)GetValue(SelectedColorProperty); }
            set { SetValue(SelectedColorProperty, value); }
        }

        public bool ShowRecentColors
        {
            get { return (bool)GetValue(ShowRecentColorsProperty); }
            set { SetValue(ShowRecentColorsProperty, value); }
        }

        public bool ShowTransparentColor
        {
            get { return (bool)GetValue(ShowTransparentColorProperty); }
            set { SetValue(ShowTransparentColorProperty, value); }
        }
    }
}
